using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Configuration;
using System.ServiceModel.Configuration;
using System.ServiceModel;
using System.Diagnostics;
using System.Xml;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using WcfSamples.DynamicProxy;
using System.Threading;
using System.IO;

namespace Spike.ContractBuilder
{
    public class ContractGenerator
    {
        private static ContractGenerator _instance;

        public static ContractGenerator Instance
        {
            get 
            {
                if (_instance == null)
                {
                    _instance = new ContractGenerator();
                }
                return _instance; 
            }            
        }

        private ContractGenerator()
        {
            _assemblies = new Dictionary<string, Assembly>();
            _contracts = new Dictionary<string, ServiceClientContract>();
        }

        #region Properties

        private Dictionary<string, Assembly> _assemblies;

        public Dictionary<string, Assembly> Assemblies
        {
            get { return _assemblies; }
            set { _assemblies = value; }
        }

        private Dictionary<string, ServiceClientContract> _contracts;

        public Dictionary<string, ServiceClientContract> Contracts
        {
            get { return _contracts; }
            set { _contracts = value; }
        }

        private Dictionary<string, ServiceHostContract> _hosts;

        public Dictionary<string, ServiceHostContract> Hosts
        {
            get { return _hosts; }
            set { _hosts = value; }
        }
	

        #endregion

        #region Methods dealing with Configuration
        public void LoadHosts()
        {            
            _hosts = new Dictionary<string, ServiceHostContract>();
            foreach (ServiceHostContract contract in IOHelper.LoadHostContracts())
            {
                foreach (ServiceHostEndpoint endpoint in contract.Endpoints.Values)
                {
                    foreach (string name in this.DetermineAssembly(endpoint.EndpointElement.Contract))
                    {
                        endpoint.InterfaceAssemblies.Add(name);     
                   
                        // now determine all the implemented located
                        Type contractType = Assemblies[name].GetType(endpoint.EndpointElement.Contract);
                        endpoint.ServiceAssemblies.AddRange(this.DetermineServiceForContract(contractType));    
                    }                    
                }
                _hosts[contract.Name] = contract;
            }
        }
        public void RefreshProxies()
        {
            ReloadAssemblies();
            
            _contracts.Clear();

            foreach (ServiceClientContract contract in IOHelper.LoadClientContracts())
            {
                foreach (ClientEndpoint endpoint in contract.Endpoints.Values)
                {
                    foreach (string name in this.DetermineAssembly(endpoint.Contract))
                    {
                        endpoint.Assemblies.Add(name);
                    }
                }
                _contracts[contract.Name] = contract;
            }
        }

        public void ReloadAssemblies()
        {
            _assemblies = IOHelper.LoadAssemblies();
        }
        public void MoveToFailedToLoad(string filename)
        {
            IOHelper.MoveFileToFailedToLoad(filename);
        }


        public delegate void StepCompletedHandler(STEP step, string message);
        public enum STEP { DownloadedMetadata, ImportedMetadata, CreatedProxy, WrittenCode, CompiledProxy, Failed }
        public event StepCompletedHandler StepCompleted;
        public void AddUrl(string name, string url)
        {
            DynamicProxyFactory factory = new DynamicProxyFactory(url);

                factory.DownloadMetadata();
                if (StepCompleted != null)
                    StepCompleted(STEP.DownloadedMetadata, string.Empty);


                factory.ImportMetadata();
                if (StepCompleted != null)
                    StepCompleted(STEP.ImportedMetadata, DynamicProxyFactory.ToString(factory.MetadataImportWarnings));


                factory.CreateProxy();
                if (StepCompleted != null)
                    StepCompleted(STEP.CreatedProxy, DynamicProxyFactory.ToString(factory.CodeGenerationWarnings));

                factory.WriteCode();
                if (StepCompleted != null)
                    StepCompleted(STEP.WrittenCode, string.Empty);

                factory.CompileProxy();
                if (StepCompleted != null)
                    StepCompleted(STEP.CompiledProxy, DynamicProxyFactory.ToString(factory.CompilationWarnings));

                string tempFile = IOHelper.AddAssembly(name, factory.ProxyAssembly.Location);
                IOHelper.SaveClientConfiguration(factory, tempFile);

                RefreshProxies();
        }        
        public void AddAssembly(string name, string filePath)
        {
            IOHelper.AddAssembly(name, filePath);
        }
        public void AddAssembly(string fileName)
        {
            this.AddAssembly(Path.GetFileNameWithoutExtension(fileName), fileName);
        }
        public string[] GetServiceContracts()
        {
            List<string> types = new List<string>();

            Type seriveType = typeof(System.ServiceModel.ServiceContractAttribute);

            foreach (Assembly assembly in Assemblies.Values)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.IsDefined(seriveType, true))
                        types.Add(type.FullName);
                }
            }
            return types.ToArray();
        }
        public List<string> DetermineAssembly(string contractType)
        {
            List<string> list = new List<string>();

            // loop by the key and add to list
            foreach (string key in Assemblies.Keys)
            {
                Assembly assembly = Assemblies[key];
                Type t = assembly.GetType(contractType, false);

                if (t != null)
                    list.Add(key);
            }

            return list;
        }
        public void LoadHost(string serviceContract)
        {
            // clear the endpoints and reload them
            _hosts[serviceContract] = IOHelper.LoadHostContract(_hosts[serviceContract].FileName);

            foreach (ServiceHostEndpoint endpoint in _hosts[serviceContract].Endpoints.Values)
            {
                foreach (string name in this.DetermineAssembly(endpoint.EndpointElement.Contract))
                {
                    endpoint.InterfaceAssemblies.Add(name);

                    // now determine all the implemented located
                    Type contractType = Assemblies[name].GetType(endpoint.EndpointElement.Contract);
                    endpoint.ServiceAssemblies.AddRange(this.DetermineServiceForContract(contractType));
                }
            }
        }
        #endregion

        #region Methods deailing with Building and Running Test Cases
        public string[] GetMethods(string assemblyName, string contractName, string endpointName)
        {
            Assembly assembly = _assemblies[assemblyName];            
            Type contractType = assembly.GetType(_contracts[contractName].Endpoints[endpointName].Contract);

            List<string> methods = new List<string>();
            foreach (MethodInfo method in contractType.GetMethods())
            {
                methods.Add(method.Name);
            }
            return methods.ToArray();
        }
        public string GenerateSampleInput(string assemblyName, string contractName, string endpointName, string methodName)
        {
            Assembly assembly = _assemblies[assemblyName];
            Type contractType = assembly.GetType(_contracts[contractName].Endpoints[endpointName].Contract);

            string ns = ContractHelper.GetNamespace(contractType);

            MethodInfo method = contractType.GetMethod(methodName);

            StringBuilder sb = new StringBuilder();

            using (XmlWriter writer = XmlTextWriter.Create(sb))
            {
                writer.WriteWhitespace("\r\n");
                writer.WriteStartElement(methodName, ns);

                foreach (ParameterInfo info in method.GetParameters())
                {
                    writer.WriteStartElement(info.Name);                        
                    writer.WriteRaw(ContractHelper.GenerateSampleXml(info));                
                    writer.WriteEndElement();
                    writer.WriteWhitespace("\r\n");
                }                
                writer.WriteEndElement();
                writer.WriteWhitespace("\r\n");
                writer.Close();
            }

            return sb.ToString();
        }
        
        public string InvokeProxyMethod(TestCase test)
        {
            ServiceClientContract contract = _contracts[test.ContractName];
            ClientEndpoint endpoint = contract.Endpoints[test.EndpointName];

            Type contractType = Assemblies[test.Assembly].GetType(endpoint.Contract);

            Type factoryType = typeof(ContractChannelFactory<>).MakeGenericType(contractType);
            Object factory = Activator.CreateInstance(factoryType, new object[] {test.EndpointName, contract.FileName});

            // hmmmmmm
            StringReader sr = new StringReader(test.Input); //test.Input);
            XmlReader xr = XmlTextReader.Create(sr);
            
            // create the channel
            IUntypedChannel channel = (IUntypedChannel)factoryType.InvokeMember("CreateUntypedChannel", BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance, null, factory, new object[] { test.URL });
            
            // determine the action
            MemberInfo s = contractType.GetMethod(test.Method);
            object[] attributes = s.GetCustomAttributes(typeof(OperationContractAttribute), true);
            if (attributes.Length == 1)
            {
                OperationContractAttribute attribute = (OperationContractAttribute)attributes[0];

                MessageVersion version = (MessageVersion)factoryType.InvokeMember("GetMessageVersion", BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance, null, factory, new object[] { });

                Message request = Message.CreateMessage(version, attribute.Action , xr);               
                Message response = channel.Post(request);

                return response.ToString();
                
            }
            else
                return "Failed to call test case...";

        }        
        private string SerializeResponse(Assembly assembly, string contract, string methodName, object response)
        {           
            Type contractType = assembly.GetType(contract);
            Type responseType = contractType.GetMethod(methodName).ReturnParameter.ParameterType;

            if(responseType.FullName=="System.Void")
                return responseType.FullName;

            if (responseType == null || !responseType.IsClass)
                return response.ToString();

            Type ser = typeof(SerializationHelper<>).MakeGenericType(responseType);

            return (string) ser.InvokeMember("SerializeServiceObject", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, null, new object[] { response });                    
        }
        private string DetermineInput(Assembly assembly, string input, out object[] parameters)
        {
            XmlParserContext context = new XmlParserContext(null, null, null, XmlSpace.None);            
            XmlTextReader reader = new XmlTextReader(input, XmlNodeType.Document, context);
            reader.WhitespaceHandling = WhitespaceHandling.None;

            List<object> objects = new List<object>();

            string methodName = string.Empty;
            while (reader.Read())
            {
                string name = reader.Name;

                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (name == "xml")
                            break;

                        if (methodName == string.Empty)
                        {
                            methodName = name;
                            break;
                        }
                        
                        if (!string.IsNullOrEmpty(name))
                        {                                                       
                            reader.Read();
                            string x;
                            if (reader.HasValue)
                                x = reader.Value;
                            else
                                x = reader.ReadOuterXml();

                            objects.Add(DeSerializeParameter(assembly, name, x));
                        }
                        break;
                }

                
            }

            parameters = objects.ToArray();

            return methodName;
        }
        private object DeSerializeParameter(Assembly assembly, string typeName, string content)
        {            
            Type contractType = assembly.GetType(typeName);

            if (contractType == null)
            {
                // retrieve from other assembly 
                Type type = Type.GetType(typeName);
                

                try
                {
                    //Assembly corLib = Assembly.GetAssembly(type);

                    if(type==typeof(string))
                        return content;

                    MethodInfo parse = type.GetMethod("Parse", new Type[] { typeof(string) });

                    if (parse != null)
                    {
                        return parse.Invoke(null, new object[] { content });
                    }
                }
                catch (Exception)
                {
                    // ignore failure to retrieve parse
                }

            }

            if (contractType.IsClass)
            {
                Type ser = typeof(SerializationHelper<>).MakeGenericType(contractType);
                return ser.InvokeMember("Deserialize", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, null, new object[] { content });
            }
            else
            {
                if (contractType.IsEnum)
                {
                    return Enum.Parse(contractType, content);
                }
                else
                    throw new Exception("Type is not supported: " + contractType.FullName);
            }
        }
        public bool CanDeserializeInput(string assemblyName, string contractName, string input, out string message)
        {
            bool canDeserialize = true;
            message = string.Empty;
            try
            {
                Assembly assembly = _assemblies[assemblyName];
                object[] parameters = null;
                string methodName = DetermineInput(assembly, input, out parameters);
            }
            catch (Exception ex)
            {
                canDeserialize = false;
                message = ex.Message;
            }

            return canDeserialize;
        }
        #endregion

        #region Methods dealing with Hosting
        private List<ServiceStub> DetermineServiceForContract(Type contractType)
        {
            List<ServiceStub> list = new List<ServiceStub>();
            foreach (Assembly assembly in Assemblies.Values)
            {
                try
                {
                    foreach (Type t in assembly.GetTypes())
                    {
                        if (t.Namespace == "Spike.ContractBuilder.Host")
                        {
                            if (contractType.IsInterface)
                            {
                                foreach (Type interfaceType in t.GetInterfaces())
                                    if (interfaceType.FullName == contractType.FullName
                                        && t.IsInterface == false)
                                    {
                                        list.Add(new ServiceStub(assembly.FullName, t));
                                    }
                            }
                            else
                            {
                                if (t.IsSubclassOf(contractType))
                                    list.Add(new ServiceStub(assembly.FullName, t));
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    string s = ex.Message;
                    
                }
            }

            return list;


        }
        public List<string> CreateStubService(string stubName, string serviceContract)
        {
            ServiceHostContract contract = _hosts[serviceContract];
            
            // used to avoid conflict
            List<string> contracts = new List<string>();

            List<string> errors = new List<string>();

            foreach (ServiceHostEndpoint endpoint in contract.Endpoints.Values)
            {
                // if we have not built the stub service for this contract
                if (!contracts.Contains(endpoint.EndpointElement.Contract))
                {
                    foreach (string assembly in endpoint.InterfaceAssemblies)
                    {
                        try
                        {
                            Type contractType = Assemblies[assembly].GetType(endpoint.EndpointElement.Contract);

                            DynamicHostFactory factory = new DynamicHostFactory(contractType);
                            
                            IOHelper.AddAssembly(stubName, factory.Location);
                        }
                        catch (Exception ex)
                        {
                            errors.Add(string.Format("Failed to build Stub Service for assembly {0}: {1}", assembly, ex.Message));
                        }
                    }

                    contracts.Add(endpoint.EndpointElement.Contract);
                }
            }

            return errors;
        }
        public void AddHost(string hostName, string contractName, string baseAddress)
        {
            string hostConfig = Resources.TestHost.Replace("HOSTNAME", hostName);
            hostConfig = hostConfig.Replace("THEADDRESS", baseAddress);

            IOHelper.AddHost(hostName, hostConfig.Replace("CONTRACT", contractName));
        }
        #endregion        

        
    
        

        #region TestResults

        public string GetTestResultsDirectory()
        {
            return IOHelper.GetTestResulsDirectory();
        }
        #endregion

        public void RemoveAssembly(string p)
        {            
            IOHelper.RemoveAssembly(p);
        }

        public void RemoveClientConfiguration(string p)
        {
            IOHelper.RemoveClientConfiguration(p);
        }
    }


}